package obscure

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/mia0x75/repeater/config"
	"github.com/mia0x75/repeater/util"
)

type Obscure struct {
	Salt string
	Key  string
	Cfg  *Config
}

var reg = [...]*regexp.Regexp{
	regexp.MustCompile(`^(\w+)\.(sub)\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)$`), // value.sub(int, int)
	regexp.MustCompile(`^(\w+)\.(left)\s*\(\s*(\d+)\s*\)\s*$`),         // value.left(int)
	regexp.MustCompile(`^(\w+)\.(right)\s*\(\s*(\d+)\s*\)$`),           // value.right(int)
	regexp.MustCompile(`^(\w+)\.(aes)\s*\(\s*\)$`),                     // value.aes()
	regexp.MustCompile(`^(\w+)\.(md5)\s*\(\s*\)$`),                     // value.md5()
	regexp.MustCompile(`^(\w+)\.(before_char)\s*\(\s*(.)\s*\)$`),       // value.before_char(char)
	regexp.MustCompile(`^(\w+)\.(after_char)\s*\(\s*(.)\s*\)$`),        // value.after_char(char)
	regexp.MustCompile(`^(\w+)\.(remove_leading)\s*\(\s*(\d+)\s*\)$`),  // value.rm_leading(int)
	regexp.MustCompile(`^(\w+)\.(remove_tailing)\s*\(\s*(\d+)\s*\)$`),  // value.rm_tailing(int)
	regexp.MustCompile(`^(\w+)\.(landline)\s*\(\s*\)$`),                // value.landline()
}

func NewObscure(salt, key string) (*Obscure, error) {
	path, err := util.GetCurrentPath()
	if err != nil {
		return nil, err
	}
	file := path + "/databases.yaml"
	cfg, err := ParseConfigFile(file)
	if err != nil {
		return nil, err
	}
	ob := &Obscure{
		Salt: salt,
		Key:  key,
		Cfg:  cfg,
	}
	return ob, nil
}

func (ob *Obscure) extract(p *config.Payload) []map[string]interface{} {
	var list []map[string]interface{}
	data := p.Event.Data

	switch p.EventType {
	case "insert", "delete":
		list = append(list, data)
	case "update":
		if _, exists := data["new_data"]; exists {
			list = append(list, data["new_data"].(map[string]interface{}))
		}
		if _, exists := data["old_data"]; exists {
			list = append(list, data["old_data"].(map[string]interface{}))
		}
	}
	return list
}

func (ob *Obscure) lookup(p *config.Payload) *TableConfig {
	schema_name := p.Database
	table_name := p.Table
	for _, db := range ob.Cfg.Databases {
		if db.Name == schema_name {
			for _, table := range db.Tables {
				if table.Name == table_name {
					return &table
				}
			}
			break
		}
	}
	return nil
}

func (ob *Obscure) match(b []byte) [][]byte {
	for _, r := range reg {
		if mb := r.FindSubmatch(b); mb != nil {
			return mb
		}
	}
	return nil
}

func (ob *Obscure) Handle(p *config.Payload) error {
	table := ob.lookup(p)
	if table == nil {
		return errors.New(fmt.Sprintf("cannot find the definition for table %s.%s.", p.Database, p.Table))
	}

	list := ob.extract(p)       // 获取完整数据
	fields := table.Fields      // 配置文件中的所有脱敏字段配置
	for _, item := range list { // 对每一个原始信息，最多两次循环（新增1次，删除1次，更新2次）
		swap := make(map[string]interface{})
		for _, field := range fields { // 对每一个脱敏字段配置

			if mb := ob.match([]byte(field.Expr)); mb != nil {
				// 必有一种脱敏规则满足
				// 查找暂存的明文信息，如果找到说明原信息不可信
				//                     -> 获取暂存信息
				//                     如果找不到说明该字段遇到第一个脱敏处理函数
				//                     -> 当前明文保存到暂存列表
				//                     -> 进行脱敏处理
				if _, found := item[string(mb[1])]; !found {
					if _, found := swap[string(mb[1])]; !found {
						item[field.Name] = nil
						// 原始字段不存在，通常应该是配置错误
						continue
					}
				} else {
					// 原始字段是否存在，因为不对原始map做删除操作，所以这个判断可信
					if _, found := swap[string(mb[1])]; !found {
						swap[string(mb[1])] = item[string(mb[1])]
						delete(item, string(mb[1]))
					} else {
						// 进入这个分支，说明原来信息被覆盖了，
						// 也就是说脱敏后的新字段和原有字段同名。
						// 所以不做任何处理，只相信临时保存的数据。
					}
				}
				item[field.Name] = ""
				if swap[string(mb[1])] == nil || swap[string(mb[1])] == "" {
					// 如果原始信息是空值
					item[field.Name] = swap[string(mb[1])]
					continue
				}
				originalValue := strings.TrimSpace(fmt.Sprintf("%v", swap[string(mb[1])]))
				r := []rune(originalValue)
				switch string(mb[2]) {
				case "md5":
					item[field.Name] = ob.Md5(originalValue)
				case "aes":
					item[field.Name] = hex.EncodeToString(util.Encrypt(util.PKCS7Pad([]byte(originalValue)), ob.Key))
				case "left":
					length, _ := strconv.ParseInt(string(mb[3]), 10, 64)
					// 确保不会出现下标越界错误
					if length <= int64(len(r)) {
						item[field.Name] = string(r[:length])
					}
				case "right":
					length, _ := strconv.ParseInt(string(mb[3]), 10, 64)
					// 确保不会出现下标越界错误
					if length <= int64(len(r)) {
						item[field.Name] = string(r[int64(len(r))-length:])
					}
				case "sub":
					start, _ := strconv.ParseInt(string(mb[3]), 10, 64)
					end, _ := strconv.ParseInt(string(mb[4]), 10, 64)
					// 确保不会出现下标越界错误
					if start < int64(len(r)) && end <= int64(len(r)) {
						item[field.Name] = string(r[start:end])
					}
				case "before_char":
					for index, c := range r {
						if string(c) == string(mb[3]) {
							item[field.Name] = string(r[:index])
						}
					}
				case "after_char":
					for index, c := range r {
						if string(c) == string(mb[3]) {
							item[field.Name] = string(r[index:])
						}
					}
				case "remove_leading":
					offset, _ := strconv.ParseInt(string(mb[3]), 10, 64)
					// 确保不会出现下标越界错误
					if offset <= int64(len(r)) {
						item[field.Name] = string(r[offset:])
					}
				case "remove_tailing":
					offset, _ := strconv.ParseInt(string(mb[3]), 10, 64)
					// 确保不会出现下标越界错误
					if offset <= int64(len(r)) {
						item[field.Name] = string(r[0 : int64(len(r))-offset])
					}
				case "landline":
					// 固话号码特别处理
					//   - 0开头，取前9位
					//   - 非0开头，取前4位
					// 确保不会出现下标越界错误
					if string(r[0]) == "0" {
						if len(r) > 9 {
							item[field.Name] = string(r[:9])
						}
					} else {
						if len(r) > 4 {
							item[field.Name] = string(r[:4])
						}
					}
				}
			}
		}
	}
	return nil
}

func (ob *Obscure) Md5(plaintext string) string {
	c := md5.New()
	c.Write([]byte(plaintext))
	c.Write([]byte(ob.Salt))
	return hex.EncodeToString(c.Sum(nil))
}

func (ob *Obscure) rand_string() string {
	str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	bt := []byte(str)
	result := []byte{}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))

	for i := 0; i < 32; i++ {
		result = append(result, bt[r.Intn(len(bt))])
	}

	return string(result)
}
